package com.hqd.ch03.v51.boot.web.embedded.tomcat;

import com.hqd.ch03.utils.ResourceUtils;
import com.hqd.ch03.v51.boot.web.server.Ssl;
import com.hqd.ch03.v51.boot.web.server.SslStoreProvider;
import com.hqd.ch03.v51.boot.web.server.WebServerException;
import com.hqd.ch03.v51.utils.StringUtils;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;

import java.io.FileNotFoundException;

class SslConnectorCustomizer implements TomcatConnectorCustomizer {

    private final Ssl ssl;

    private final SslStoreProvider sslStoreProvider;

    SslConnectorCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) {
        this.ssl = ssl;
        this.sslStoreProvider = sslStoreProvider;
    }

    @Override
    public void customize(Connector connector) {
        ProtocolHandler handler = connector.getProtocolHandler();
        configureSsl((AbstractHttp11JsseProtocol<?>) handler, this.ssl, this.sslStoreProvider);
        connector.setScheme("https");
        connector.setSecure(true);
    }

    /**
     * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL.
     *
     * @param protocol         the protocol
     * @param ssl              the ssl details
     * @param sslStoreProvider the ssl store provider
     */
    protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl, SslStoreProvider sslStoreProvider) {
        protocol.setSSLEnabled(true);
        SSLHostConfig sslHostConfig = new SSLHostConfig();
        sslHostConfig.setHostName(protocol.getDefaultSSLHostConfigName());
        sslHostConfig.setSslProtocol(ssl.getProtocol());
        protocol.addSslHostConfig(sslHostConfig);
        configureSslClientAuth(sslHostConfig, ssl);
        SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.UNDEFINED);
        if (ssl.getKeyStorePassword() != null) {
            certificate.setCertificateKeystorePassword(ssl.getKeyStorePassword());
        }
        if (ssl.getKeyPassword() != null) {
            certificate.setCertificateKeyPassword(ssl.getKeyPassword());
        }
        if (ssl.getKeyAlias() != null) {
            certificate.setCertificateKeyAlias(ssl.getKeyAlias());
        }
        sslHostConfig.addCertificate(certificate);
        String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
        if (StringUtils.hasText(ciphers)) {
            sslHostConfig.setCiphers(ciphers);
        }
        configureEnabledProtocols(protocol, ssl);
        if (sslStoreProvider != null) {
            configureSslStoreProvider(protocol, sslHostConfig, certificate, sslStoreProvider);
            String keyPassword = sslStoreProvider.getKeyPassword();
            if (keyPassword != null) {
                certificate.setCertificateKeyPassword(keyPassword);
            }
        } else {
            configureSslKeyStore(certificate, ssl);
            configureSslTrustStore(sslHostConfig, ssl);
        }
    }

    private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
        if (ssl.getEnabledProtocols() != null) {
            for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
                sslHostConfig.setProtocols(StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
            }
        }
    }

    private void configureSslClientAuth(SSLHostConfig config, Ssl ssl) {
        if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
            config.setCertificateVerification("required");
        } else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
            config.setCertificateVerification("optional");
        }
    }

    protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
                                             SSLHostConfigCertificate certificate, SslStoreProvider sslStoreProvider) {
        try {
            if (sslStoreProvider.getKeyStore() != null) {
                certificate.setCertificateKeystore(sslStoreProvider.getKeyStore());
            }
            if (sslStoreProvider.getTrustStore() != null) {
                sslHostConfig.setTrustStore(sslStoreProvider.getTrustStore());
            }
        } catch (Exception ex) {
            throw new WebServerException("Could not load store: " + ex.getMessage(), ex);
        }
    }

    private void configureSslKeyStore(SSLHostConfigCertificate certificate, Ssl ssl) {
        try {
            certificate.setCertificateKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
        } catch (Exception ex) {
            throw new WebServerException("Could not load key store '" + ssl.getKeyStore() + "'", ex);
        }
        if (ssl.getKeyStoreType() != null) {
            certificate.setCertificateKeystoreType(ssl.getKeyStoreType());
        }
        if (ssl.getKeyStoreProvider() != null) {
            certificate.setCertificateKeystoreProvider(ssl.getKeyStoreProvider());
        }
    }

    private void configureSslTrustStore(SSLHostConfig sslHostConfig, Ssl ssl) {
        if (ssl.getTrustStore() != null) {
            try {
                sslHostConfig.setTruststoreFile(ResourceUtils.getURL(ssl.getTrustStore()).toString());
            } catch (FileNotFoundException ex) {
                throw new WebServerException("Could not load trust store: " + ex.getMessage(), ex);
            }
        }
        if (ssl.getTrustStorePassword() != null) {
            sslHostConfig.setTruststorePassword(ssl.getTrustStorePassword());
        }
        if (ssl.getTrustStoreType() != null) {
            sslHostConfig.setTruststoreType(ssl.getTrustStoreType());
        }
        if (ssl.getTrustStoreProvider() != null) {
            sslHostConfig.setTruststoreProvider(ssl.getTrustStoreProvider());
        }
    }

}
